    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Xml;
    using System.Collections.Generic;
    using Microsoft.Build.Utilities;
    using Microsoft.Win32;

using Microsoft.Build.Framework;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Proxy;
using Microsoft.TeamFoundation.Client;
using BuildStepStatus = Microsoft.TeamFoundation.Build.Client.BuildStepStatus;

namespace TeamDeploy.Tasks
    {
        /// <summary>
        /// Deploys MSI to target machines
        /// </summary>
        public class Deploy : TaskBase
        {
            private static XmlDocument deployDocument;
            private static object workerLocker = new object();
            private static int runningWorkers;
            private static bool machineDeployedFailed;



            public override bool Execute()
            {
                // Run Installs to Target machines 

                string trackingStep = "";
                
                try
                {

                    InitializeBuildStep("Deploying build to target machines", "Running deploy to target machines");

                    machineDeployedFailed = false;
                    trackingStep = "Loading DeployScript";
                    deployDocument = new XmlDocument();
                    deployDocument.Load(DeployScript);

                    XmlNodeList targetMachinesNodes;

                    //Get a list of all target machines 
                    targetMachinesNodes = deployDocument.SelectNodes("DeploymentScript/TargetMachines/TargetMachine");

                    if (targetMachinesNodes == null)
                    {

                        Log.LogError("No target machines were specified in the build script.");
                        UpdateBuildStep(false);
                        return false;
                    }

                    runningWorkers = targetMachinesNodes.Count;

                    for (int targetMachineLoop = 0; targetMachineLoop < runningWorkers; targetMachineLoop++ )
                    {
                        ThreadPool.QueueUserWorkItem(DeployToTargetMachine, targetMachineLoop);
                    }

                    lock (workerLocker)
                    {
                        while (runningWorkers > 0) Monitor.Wait(workerLocker);
                    }

                    if (machineDeployedFailed != true)
                    {
                        UpdateBuildStep(true);
                        return true;
                    }
                    else
                    {
                        UpdateBuildStep(false);
                        return false;
                    }

                }
                catch (Exception ex)
                {
                    if (BuildEngine != null)
                    {
                        Log.LogError("Error executing deploy in step: " + trackingStep + " :::" + ex);
                    }
                    UpdateBuildStep(false);
                    return false;
                }
            }


            private void DeployToTargetMachine(object instance)
            {
                string targetMachine = "";
                string trackingStep = "";
                
                string serviceName;
                StopService stopTask;
                StartService startTask;
                try 
                {
                    XmlNodeList targetMachinesNodes;

                    //Get a list of all target machines 
                    targetMachinesNodes = deployDocument.SelectNodes("DeploymentScript/TargetMachines/TargetMachine");


                    XmlNode TargetMachineNode = targetMachinesNodes[(int) instance];

                    XmlNodeList nodeList;
                    XmlNodeList msiList;
                    string nodeValue;


                    targetMachine = TargetMachineNode.Attributes["name"].Value;

                //Cleanup PsExec if the node is specified
                nodeList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/CleanupPsExec");
                if (nodeList != null)
                {
                    CleanupPsExec cleanup = new CleanupPsExec();
                    cleanup.BuildEngine = BuildEngine;
                    cleanup.TargetMachine = targetMachine;
                    cleanup.KillAppPathFilename = KillAppPathFilename;
                    cleanup.Execute();
                }

                //Get a list of all process names to be killed (if any are specified) 
                nodeList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/KillProcess");

                    if ((nodeList != null))
                    {
                        foreach (XmlNode KillProcess in nodeList)
                        {
                            nodeValue = KillProcess.Attributes["name"].Value;
                            trackingStep = "Killing Process" + nodeValue;

                            KillProcess kill = new KillProcess();
                            kill.BuildEngine = BuildEngine;
                            kill.TargetMachine = targetMachine;
                            kill.KillAppPathFilename = KillAppPathFilename;
                            kill.ProcessName = nodeValue;
                            kill.IgnoreExitCode = "true";
                            kill.Execute();
                        }
                    }

                    msiList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/MSI");

                    foreach (XmlNode MSI in msiList)
                    {
                        string uninstallProductCode;
                        string uninstallProductName;
                        string msiPathFile;
                        string overrideDefaultArgs;
                        string overridePSExecArgs;
                        string extraArgs;

                        uninstallProductCode = GetNodeValue(MSI, "UninstallProductCode");
                        msiPathFile = MSI["MSIPathFile"].InnerText;
                        overrideDefaultArgs = GetNodeValue(MSI, "OverrideDefaultArgs");
                        extraArgs = GetNodeValue(MSI, "ExtraArgs");
                        serviceName = GetNodeValue(MSI, "ServiceName");
                        uninstallProductName = GetNodeValue(MSI, "UninstallProductName");
                        overridePSExecArgs = GetNodeValue(MSI, "OverridePSExecArgs");

                    //If this MSI package is for a Windows service then stop the service 
                    if ((!string.IsNullOrEmpty(serviceName)))
                    {
                        stopTask = new StopService();
                        stopTask.BuildEngine = BuildEngine;
                        stopTask.TargetMachine = targetMachine;
                        stopTask.ServiceName = serviceName;
                        trackingStep = "Stopping service: " + serviceName;
                        stopTask.IgnoreExitCode = "true";
                        stopTask.Execute();
                    }

                        //If an uninstall product code was specified, then uninstall the MSI before proceeding 
                        if ((!string.IsNullOrEmpty(uninstallProductCode)))
                        {
                            trackingStep = "Uninstalling " + uninstallProductCode + " usinging remoteExecute " +
                                           RemoteExecutePathFilename;

                            Uninstall uninstallTask = new Uninstall();
                            uninstallTask.BuildEngine = BuildEngine;
                            uninstallTask.ProductCode = uninstallProductCode;
                            uninstallTask.RemoteExecutePathFilename = RemoteExecutePathFilename;
                            uninstallTask.TargetMachine = targetMachine;
                            uninstallTask.IgnoreExitCode = "true";
                            uninstallTask.Execute();
                        }

                        //If an uninstlal product name was specified, then uninstall all applications that 
                        //start with the uninstall product name 
                        if ((!string.IsNullOrEmpty(uninstallProductName)))
                        {
                            trackingStep = "getting UninstalledProductCodes by ProductName";
                            List<string> uninstallList = GetUninstallProductCodes(uninstallProductName, targetMachine);

                            //Uninstall each product code matching the UninstallProductName 
                            if (uninstallList != null && uninstallList.Count > 0)
                            {
                                foreach (string code in uninstallList)
                                {
                                    trackingStep = "Uninstalling " + code;

                                    Uninstall uninstallTask = new Uninstall();
                                    uninstallTask.BuildEngine = BuildEngine;
                                    uninstallTask.ProductCode = code;
                                    uninstallTask.RemoteExecutePathFilename = RemoteExecutePathFilename;
                                    uninstallTask.TargetMachine = targetMachine;
                                    uninstallTask.Execute();
                                }
                            }
                        }


                        //Get a list of all files to be deleted (if any) 
                        nodeList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/DeleteFile");

                        if ((nodeList != null))
                        {
                            foreach (XmlNode fileNode in nodeList)
                            {
                                nodeValue = fileNode.Attributes["path"].Value;

                                trackingStep = "DeleteFiles " + nodeValue;

                                DeleteFiles(targetMachine, nodeValue);
                            }
                        }

                        //Get a list of all folders (and contained files) to be deleted (if any) 
                        nodeList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/DeleteFolder");

                        if ((nodeList != null))
                        {
                            foreach (XmlNode fileNode in nodeList)
                            {
                                nodeValue = fileNode.Attributes["path"].Value;

                                trackingStep = "Deleting Folder " + nodeValue;

                                DeleteFolder(targetMachine, nodeValue);
                            }
                        }


                        trackingStep = "Installing MSI " + msiPathFile;
                        //Install the MSI package 
                        Install installTask = new Install();
                        installTask.BuildEngine = BuildEngine;
                        installTask.RemoteExecutePathFilename = RemoteExecutePathFilename;
                        installTask.MSIPathFilename = msiPathFile;
                        installTask.ExtraArgs = extraArgs;
                        installTask.OverrideDefaultArgs = overrideDefaultArgs;
                        installTask.OverridePSExecArgs = overridePSExecArgs;
                        installTask.TargetMachine = targetMachine;
                        installTask.Execute();

                        //If this MSI package is for a Windows service then restart the service 
                        if ((serviceName.Length > 0))
                        {
                            trackingStep = "Starting Service " + serviceName;

                            startTask = new StartService();

                            startTask.TargetMachine = targetMachine;
                            startTask.ServiceName = serviceName;

                            startTask.Execute();
                        }
                    }
                    Log.LogMessage("Successfully deployed to machine:" + targetMachine + ".");
                }

            catch (Exception ex)
            {
                machineDeployedFailed = true;
                Log.LogError("Error executing deploy in step: " + trackingStep + "to targetMachine: " + targetMachine +  " :::" + ex);
            }
            finally
            {
                lock (workerLocker)
                {
                    runningWorkers--; Monitor.Pulse(workerLocker);
                }
            }
        }


            /// <summary>
            /// Path to the XML Deployment script.
            /// </summary>
            [Required]
            public string DeployScript { get; set; }

            /// <summary>
            /// Full path and utility that will kill any specified applications on the target machines. (designed for pskill.exe)
            /// </summary>
            [Required]
            public string KillAppPathFilename { get; set; }

            /// <summary>
            /// Full path and utility to execute the MSI installer on the target machines.  (designed for psexec.exe)
            /// </summary>
            [Required]
            public string RemoteExecutePathFilename { get; set; }


            private static string GetNodeValue(XmlNode nodeList, string nodeName)
            {
                XmlNode searchNode;

                searchNode = nodeList.SelectSingleNode(nodeName);

                if ((searchNode != null))
                {
                    //The "ServiceName" tag was found so let's obtain the value 
                    return searchNode.InnerText.Trim();
                }
                else
                {
                    //If the "ServiceName" tag is not present then we assume it is not a service 
                    return string.Empty;
                }
            }


            private void DeleteFiles(string machineName, string path) //, string remoteExecutePathFilename)
            {
                try
                {
                    string ProcessArgs = "\\\\" + machineName + " -accepteula CMD /C DEL /Q \"" + path + "\"";

                    

                    Process p = Process.Start(RemoteExecutePathFilename, ProcessArgs);
                    p.WaitForExit();

                    Log.LogMessage("Delete Files: " + path + " with return code:" + p.ExitCode);

                }
                catch (Exception)
                {
                   
                   Log.LogMessage(string.Format("Could not delete files '{0}' on machine {1}.", path, machineName));
                   throw;
                }
            }

            private void DeleteFolder(string machineName, string path)
            {
                try
                {
                    string ProcessArgs = "\\\\" + machineName + " -accepteula CMD /C RMDIR /S /Q \"" + path + "\"";

                    Log.LogMessage("Delete Folder: " + path);

                   Process.Start(RemoteExecutePathFilename, ProcessArgs);
                }
                catch (Exception)
                {

                    Log.LogMessage(string.Format("Could not delete folder'{0}' on machine {1}.", path, machineName));
                    throw;
                }

        }
        public List<string> GetUninstallProductCodes(string uninstallProductName, string targetMachine)
        {
            try
            {
                uninstallProductName = uninstallProductName.Trim().ToLower();
                List<string> codes = new List<string>();
                RegistryKey regKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, targetMachine);
                RegistryKey tempKey;

                regKey = regKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", false);

                    foreach (string subKey in regKey.GetSubKeyNames())
                    {
                        tempKey = regKey.OpenSubKey(subKey, false);

                        if (Array.IndexOf(tempKey.GetValueNames(), "DisplayName") >= 0)
                        {
                            if (tempKey.GetValue("DisplayName").ToString().ToLower()
                                .StartsWith(uninstallProductName))
                            {
                                codes.Add(subKey);
                            }
                        }
                    }

                regKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, targetMachine);

                regKey = regKey.OpenSubKey(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall", false);

                if (regKey != null)
                {
                    foreach (string subKey in regKey.GetSubKeyNames())
                    {
                        tempKey = regKey.OpenSubKey(subKey, false);

                        if (Array.IndexOf(tempKey.GetValueNames(), "DisplayName") >= 0)
                        {
                            if (tempKey.GetValue("DisplayName").ToString().ToLower()
                                .StartsWith(uninstallProductName))
                            {
                                codes.Add(subKey);
                            }
                        }
                    }
                }

                return codes;
            }
            catch (Exception ex)
            {
                Log.LogMessage(string.Format("Could not Get Uninstall Product Codes for Name '{0}' on machine {1}. Exception: {2}", uninstallProductName, targetMachine, ex.ToString()));
                throw;
            }
        }
    }
}
